
tree grammar PxlTreeWalker;

options {
    tokenVocab = Pxl;
    ASTLabelType = CommonTree;
}

@header {
package pxl.grammar.classes;

import pxl.*;
import pxl.errors.*;
import pxl.types.*;
import pxl.types.assignment.*;
import pxl.types.util.*;
import com.google.common.collect.*;

}

file_input returns [PxlObject res]
    : (r=stmt)* { res = r; } ;
    

interactive_input returns [PxlObject res]
    : NULL_STMT { res = None.instance(); }
    | r=stmt    { res = r; }
    | EOF_STMT	{ res = null; }
    ;

stmt returns [PxlObject res]
@init {
}
    : assignment_stmt
    | value=expr              { $res = value; }
    | function
    | if_else
    | while_do
    | for_loop
    | 'break'                { BreakException.throwBreak();               }
    | 'continue'             { ContinueException.throwContinue();         }
    | ^('return' value=expr) { ReturnException.throwValue( value ); }
    | 'pass'
    | assert_stmt
    | import_stmt        
    | ^(PLUSEQUAL a=expr b=expr) { a.__iadd__( b ); }
    | ^(MINUSEQUAL a=expr b=expr) { a.__isub__( b ); }
    ;


expr returns [PxlObject value]
@init {
    $value = None.instance();
}
    :   ^(UNARY_PLUS  a=expr )                     { $value = a.__pos__(); }
    |   ^(UNARY_MINUS a=expr )                     { $value = a.__neg__(); }
    |   ^(TILDE      a=expr )                     { $value = a.__invert__(); }
    |   ^(PLUS       a=expr b=expr)	             { $value = a.__add__( b );         }
    |   ^(MINUS       a=expr b=expr)	             { $value = a.__sub__( b );     } 
    |   ^(STAR       a=expr b=expr)	             { $value = a.__mul__( b );     }
    |   ^(SLASH       a=expr b=expr)	             { $value = a.__truediv__( b );     }
    |   ^(DOUBLESLASH a=expr b=expr)	             { $value = a.__floordiv__( b );     }
    |   ^(PERCENT       a=expr b=expr)                   { $value = a.__mod__( b );     }
    |   ^(CALL      call_args=args_list i=expr ) { $value = i.__call__( call_args );   }
    |   ^(SUBSCRIPT b=expr i=expr)        { $value = i.__getitem__( b ); }
    |   ^(GET_ATTR  id=identifier i=expr ) { $value = i.__getattr__( id );      }
    |   ^('and'    a=expr n=expr_node)     { if ( a.__bool__() == PxlBoolean.False )
                                                $value = a;
                                             else {
                                                 TreeNodeStream stream = new CommonTreeNodeStream( n );
                                                 PxlTreeWalker walker = new PxlTreeWalker( stream );
                                                 $value = walker.expr();
                                             } 
                                            }
    |   ^('or'     a=expr n=expr_node)          { if ( a.__bool__() == PxlBoolean.True )
                                                $value = a;
                                             else {
                                                 TreeNodeStream stream = new CommonTreeNodeStream( n );
                                                 PxlTreeWalker walker = new PxlTreeWalker( stream );
                                                 $value = walker.expr();
                                             } 
                                            }
    |   ^('not'    a=expr )                         { $value = a.__not__();              }
    |	^(VBAR		   a=expr b=expr)					{ $value = a.__or__( b );     }
    |	^(CIRCUMFLEX   a=expr b=expr)					{ $value = a.__xor__( b );     }
    |	^(AMPER		   a=expr b=expr)					{ $value = a.__and__( b );     }
    | ^(DOUBLESTAR      a=expr b=expr)         { $value = a.__pow__( b );     }
    | ^(LEFTSHIFT      a=expr b=expr)         { $value = a.__lshift__( b );     }
    | ^(RIGHTSHIFT      a=expr b=expr)         { $value = a.__rshift__( b );     }
    |   comp=COMPARISON                                    { $value = ((ComparisonTree)comp).evaluate(); }
    |   a=lambda                                        { $value = a;                  }
    |   c=constant                                   { $value = c;                              }
    |   id=identifier		                     { $value = Context.getInstance().get( id ); }
    |   args=testlist            { if ( args.size() == 1 ) 
                                       $value = args.get( 0 );
                                   else if ( args.size() > 1 )
                                       $value = new PxlTuple( args );
                                 }
    ;

assert_stmt
    : ^('assert' cond=expr text=expr? )
        {
            if ( cond.__bool__() == PxlBoolean.False )
            {
                if ( text == null )
                    throw new pxl.errors.AssertionError();
                else
                    throw new pxl.errors.AssertionError( text.toString() );
            }
        }
    ;
        
assignment_stmt 
@init {
}
    // Actually a name can be a Java String that means a new PxlObject to be
    // created or a PxlObject with subscripts and field accesses.
    :   ^('=' names=left_values values=testlist)
        { 
            int n1 = names.size();
            int n2 = values.size();
            if ( n2 == 1 && n1 > 1 )
                throw new TypeError( "unpack non-sequence" );
            if ( n2 > n1 && n1 != 1 )
                throw new ValueError( "too many values to unpack" );
            if ( n2 < n1 )
                throw new ValueError( "need more than " + n2 + " values to unpack" );
           
            if ( n1 == 1 && n2 > 1 ) {
                // Creates a new Tuple on the fly
                List<PxlObject> tmp_values = values;
                values = new ArrayList<PxlObject>( 1 );
                values.add( new PxlTuple( tmp_values ) );
            }
            
            // Context.getInstance().put( name, value );  
            for ( int i=0; i < n1; i++ )
                 names.get( i ).assignValue( values.get( i ) );
        } 
    ;

left_values returns [List<LeftValue> names]
@init {
	names = new ArrayList<LeftValue>();
}
    :   ^(TEST_LIST (value=left_value {names.add(value);} )+ )
    ;

left_value returns [LeftValue value]
    :  n=NAME { value = new LeftValueName( n.getText() ); }
    |  ^(SUBSCRIPT b=expr a=expr ) { value = new LeftValueSetItem( a, b );}
    |  ^(GET_ATTR  id=identifier a=expr) { value = new LeftValueSetAttr( a, id ); }
    ;

identifier returns [ String name ]
    :
        i=NAME { name = $i.text; }
    ;

identifierValue returns [ PxlObject obj ]
    :  ^(SUBSCRIPT a=identifierValue b=expr) { $obj = a.__getitem__( b );}
    |  ^(GET_ATTR a=identifierValue id=identifier) { $obj = a.__getattr__( id ); }
    |  i=NAME 
       { 
           obj = Context.getInstance().get( $i.text );
       }
    ;

constant returns [PxlObject res]
@init {  
    $res = None.instance();
} 
    :   i=INT			{ $res = PxlInteger.fromString( i.getText(), 10 ); }
    |   h=HEX			{ $res = PxlInteger.fromString( h.getText(), 16 ); }
    |   o=OCTAL 		{ $res = PxlInteger.fromString( o.getText(),  8 ); }
    |   f=FLOAT			{ $res = PxlFloat.fromString( f.getText() ); }
    |	s=STRING		{ $res = PxlString.parse( s.getText() ); }
    |   ST_NONE	        { $res = None.instance(); }
    |   ST_TRUE			{ $res = PxlBoolean.True; }
    |   ST_FALSE 		{ $res = PxlBoolean.False; }
    |   a=list          { $res = a; } 
    |	x=set			{ $res = x; }
    |   ^(MAP b=map_entries)    { if ( b == null ) b = new PxlMap(); $res= b; }
    ;

testlist returns [List<PxlObject> list]
@init {
    $list = Lists.newArrayList();
}
:   ^(TEST_LIST (e=expr { list.add( e );})*)
    ;

args_list returns [List<PxlArgument> list]
@init {
    $list = Lists.newArrayList();
}
    :    ^( ARGS_LIST 
            (e=expr { list.add( new PxlArgument(e) );})* 
            ( ^(KEYWORDS (n=identifier e=expr
                       { list.add( new PxlArgument( n, e ) ); }
                     )+ ) )*

            ( STAR t=expr { PxlArgument arg = new PxlArgument( t );
                            arg.setIsTuple();
                            list.add( arg );
                          } 
            )?
            ( DOUBLESTAR t=expr { PxlArgument arg = new PxlArgument( t );
                                  arg.setIsMap();
                                  list.add( arg );
                                } 
            )?
         )
    ;

list returns [PxlList list]
@init {
    $list = new PxlList();
}
:   ^(LIST (e=expr { list.add( e );})*)
    ;
    
set returns [PxlSet res]
@init {
	$res = new PxlSet();
}
	: ^(SET (t=expr { $res.add( t ); } )+)
	;

map_entries returns [PxlMap map]
@init {
	$map = new PxlMap();
}
    : ( key=expr value=expr  { map.put( key, value ); }
      )*
    ;
    
function 
    : ^( ST_FUNCTION name=identifier args=parameters code=codeblock )
        { 
            PxlFunction func = new PxlFunction( name, args, code, 
            			Context.getInstance().getCurrentScope(), false ); 

            // Save the function in the current scope
            Context.getInstance().put( name, func );
        }
    ;
    

if_else
@init {
	boolean executed = false;
}
    :
        ^(ST_IF (cond=expr then_=codeblock )+
        {
        	if ( ! executed && cond.__bool__().getValue() ) 
        	{
        		executed = true;
            	TreeNodeStream stream = new CommonTreeNodeStream( then_ );
                PxlTreeWalker walker = new PxlTreeWalker( stream );
                walker.stmtlist();
            }
        }
        
        (else_=codeblock 
        { 
        	if ( ! executed )
			{
				TreeNodeStream stream = new CommonTreeNodeStream( else_ );
				PxlTreeWalker walker = new PxlTreeWalker( stream );
        		walker.stmtlist();
			}
		} )? 
       )
    ;
lambda returns [PxlObject res]
    : ^( ST_LAMBDA args=parameters code=expr_node )
        { 
            res = new PxlFunction( "<lambda>", args, code, 
            			Context.getInstance().getCurrentScope(), true ); 
        }
    ;


while_do
    : ^(ST_WHILE cond=expr_node code=codeblock code_else=codeblock?)
        {
            PxlObject res;
            PxlTreeWalker walker;
            TreeNodeStream stream;

            while ( true ) {
                stream = new CommonTreeNodeStream( cond );
                walker = new PxlTreeWalker( stream );
                res = walker.expr();

                if ( res.__bool__() != PxlBoolean.True )
                {
                	if ( code_else != null )
                	{
               			stream = new CommonTreeNodeStream( code_else );
		                walker = new PxlTreeWalker( stream );
        		        walker.stmtlist();
                	}
                    break;
                }

				try 
				{
                	stream = new CommonTreeNodeStream( code );
                	walker = new PxlTreeWalker( stream );
                	walker.stmtlist();
                } 
                catch ( BreakException e )
                {
                	break;
                }
                catch ( ContinueException e )
                {
                	continue;
                }
            }
        }
    ;
    
for_loop
    : ^(ST_FOR var=identifier l=expr code=codeblock code_else=codeblock?)
        {
            PxlTreeWalker walker;
            TreeNodeStream stream;
            
            PxlIterator it = l.__getiterator__();
            
            if ( code_else != null && it.hasNext() == PxlBoolean.False )
            {
            	stream = new CommonTreeNodeStream( code_else );
				walker = new PxlTreeWalker( stream );
                walker.stmtlist();
            }
        
            while ( it.hasNext() == PxlBoolean.True ) {
                try {
	                Context.getInstance().put( var, it.next() );

                    stream = new CommonTreeNodeStream( code );
                    walker = new PxlTreeWalker( stream );
                    walker.stmtlist();

                } catch ( ContinueException ce ) {
                    continue;
                } catch ( BreakException e ) {
                    // the loop breaked
                    break;
                } catch ( Throwable t ) {
                    throw new RuntimeException( t );
                }
            }
        
        }
    ;

import_stmt 
    :   ^(ST_IMPORT (name=dotted_as_name { 
    		/* JavaClassFactory.getInstance().importClass( name ); */ 
    		// System.out.println( "import " + $name.name + " as " + $name.alias );
    		PxlModule mod = PxlModule.importModule( $name.name );
    		if ( $name.alias == null )
    			Context.getInstance().put( mod.name(), mod );
    		else 
    			Context.getInstance().put( $name.alias, mod );
    	}
    	)+) 
    ;

dotted_as_name returns [ String name, String alias ]
    : ^('as' n=NAME a=NAME) { $name = n.getText(); $alias = a.getText(); }
    | n=NAME                { $name = n.getText(); $alias = null; }
    ;

parameters returns [ PxlParametersList parameters ]
@init {
	parameters = new PxlParametersList();
}
    :  ^(PARAMETERS 
            ( id=identifier { parameters.add( new PxlParameter(id) ); } )* 
            ( ^('=' id=identifier e=expr { PxlParameter p = new PxlParameter( id, e ); 
                                           p.setIsOptional(true);
                                           parameters.add( p ); } ) )*
            ( ^(STAR id=identifier { PxlParameter p = new PxlParameter( id ); 
                                 p.setIsTuple(true);
                                 parameters.add( p ); } ) )?
            ( ^(DOUBLESTAR id=identifier { PxlParameter p = new PxlParameter( id ); 
                                  p.setIsMap(true);
                                  parameters.add( p ); } ) )?
        ) 
    ;

stmtlist 
    : ^(STMT_LIST stmt+) ;

codeblock returns [ CommonTree node ]
    : ^(n=STMT_LIST .+) { $node = n; }  ;

expr_node returns [ CommonTree node ]
	: n=. { $node = n; if ($node == null) throw new RecognitionException(); } ;
	
